package com.lj.demo.springbootshirojwt.shiro;

import com.auth0.jwt.JWT;
import com.google.common.collect.Maps;
import com.lj.demo.springbootshirojwt.exception.AuthorizationException;
import com.lj.demo.springbootshirojwt.util.*;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Title: 用户realm
 * Description:
 * Date: 2019年06月26日 14:30
 *
 * @author lvjie@hiynn.com
 * @version 1.0
 * Significant Modify：
 * Date                  Author                 Content
 * =================================================================
 * 2019年06月26日         lvjie@hiynn.com         创建文件,实现基本功能
 * =================================================================
 */
@Component
public class UserRealm extends AuthorizingRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserRealmByRedis.class);

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private HttpServletResponse response;

    /**
     * 校验token是否是自定义的token
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 权限校验
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        String token = principals.getPrimaryPrincipal().toString();
        info.addRoles(JWTUtil.getRoles(token));
        info.addStringPermissions(JWTUtil.getPermissions(token));
        return info;
    }

    /**
     * 登录认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        JWTToken jwtToken = (JWTToken) authenticationToken;
        //获取token
        String token =jwtToken.getPrincipal().toString();
        //获取用户姓名
        String userName = JWTUtil.getUserName(token);
        String userNameAndIp = userName+"_"+ HttpRequestUtil.getIpAdrress(request);
        //判断用户是否注销登录或修改密码等操作
        if (Constants.blackList.contains(userNameAndIp)){
            throw new AuthorizationException("用户已退出登录，token已失效，请重新登录");
        }
        //获取refreshToken
        String refreshToken = HttpRequestUtil.getRefreshTokenFromRequest(request);
        //获取密码
        String password = Constants.passwordContainer.get(userName);
        //检验token是否有效
        Long tokenExpireTime = JWTUtil.getExpireTime(token);
        Long refreshTokenExpireTime = JWTUtil.getExpireTime(refreshToken);
        long time = System.currentTimeMillis()/1000;
        //token和refreshToken都过期  则重新登录
        if (time > refreshTokenExpireTime && time > tokenExpireTime){
            throw new AuthorizationException("token已失效请重新登录");
        }
        //判断token是否过期
        if (time > tokenExpireTime && time < refreshTokenExpireTime){
            //token过期且refreshToken没过期 则重新签发token
            getNewToken(token,Constants.TOKEN_KEY);
        }else {
            //没有过期   校验token是否合法
            if (!JWTUtil.verify(token,password)){
                throw new AuthorizationException("token不合法");
            }
        }
        //判断refreshToken是否过期
        if (time < tokenExpireTime && time > refreshTokenExpireTime){
            //refreshToken过期且token没过期 则重新签发refreshToken
            getNewToken(refreshToken,Constants.REFRESH_TOKEN_KEY);
        }else {
            //没有过期   校验refreshToken是否合法
            if (!JWTUtil.verify(refreshToken,userName,password)){
                throw new AuthorizationException("token不合法");
            }
        }
        return new SimpleAuthenticationInfo(token, token, getName());
    }

    /**
     * 检验token是否合法
     * @param token 需要检验的token
     * @param type 检验的类型
     * @return 是否合法
     */
    public void getNewToken(String token,String type){
        //获取用户姓名
        String userName = JWTUtil.getUserName(token);
        String userNameAndIp = userName+"_"+ HttpRequestUtil.getIpAdrress(request);
        //获取密码
        String password = Constants.passwordContainer.get(userName);
        //获取URI
        String requestURI = request.getRequestURI().replace("/","_");
        //获取当前时间戳，用于判断token是否失效
        long time = System.currentTimeMillis()/1000;
        //存储token的key
        String key = "";
        //判断是refreshToken还是token
        if (Objects.equals(type,Constants.TOKEN_KEY)){
            //tokenKey
            key = Constants.TOKEN_KEY+Constants.SEPARATOR+userNameAndIp;
        }else if (Objects.equals(type,Constants.REFRESH_TOKEN_KEY)){
            //refreshTokenKey
            key = Constants.REFRESH_TOKEN_KEY+Constants.SEPARATOR+userNameAndIp;
        }
        /**
         * key+uri组成新的uriKey  规则：token_/refreshToken_userName_ip_uri  例：token_wangwu_192.168.1.111_shiro-jwt_resource_updateResource
         * 精确到请求的方法，用于确定每一个方法同一用户同一IP每次签发只能更新一次token
         */
        String uriKey = key+requestURI;
        //新的token
        String newToken = null;
        synchronized (UserRealm.class){
           //防止同一ip+同一用户（即同一页面） 多次携带失效的token请求同一方法  检查是否已经签发过了  如果已经签发过了并且有效则返回存在的值
           if (Constants.userRequestStatusContainer.containsKey(uriKey) && Constants.userRequestStatusContainer.get(uriKey)){
               throw new AuthorizationException("该URI："+requestURI+"已申请过新的"+type+"，请使用新的"+type+"访问");
           }
           //防止同一ip+同一用户（即同一页面）  并发请求不同方法
           if (Constants.tokenContainer.containsKey(key) && JWTUtil.getExpireTime(Constants.tokenContainer.get(key)) > time){
               //从容器中获取新的token
               newToken = Constants.tokenContainer.get(key);
           }else {
               if (Objects.equals(type,Constants.TOKEN_KEY)){
                   //获取角色信息
                   List<String> roles = JWTUtil.getRoles(token);
                   //获取权限信息
                   List<String> permissions = JWTUtil.getPermissions(token);
                   newToken = JWTUtil.sign(userName, password, Constants.REFRESH_TOKEN_EXPIRE_TIME,roles,permissions);
               }else if (Objects.equals(type,Constants.REFRESH_TOKEN_KEY)){
                   newToken = JWTUtil.sign(userName, password, Constants.REFRESH_TOKEN_EXPIRE_TIME);
               }
               //存储用户token 避免并发请求创建不同的token返回前端
               Constants.tokenContainer.put(key,newToken);
           }
         /*
            表示该URI已经换取过新的token,每次签发后每个请求URI(即Controller方法)只能从容器里面获取一次
            防止新token返回给前端，前端不替换一直用旧token换取新token
         */
         Constants.userRequestStatusContainer.put(uriKey,true);
         //交替置换为false  允许多次交替更新
        if (Objects.equals(type,Constants.TOKEN_KEY)){
            Constants.userRequestStatusContainer.put(Constants.REFRESH_TOKEN_KEY+Constants.SEPARATOR+userNameAndIp+requestURI,false);
        }else if (Objects.equals(type,Constants.REFRESH_TOKEN_KEY)){
            Constants.userRequestStatusContainer.put(Constants.TOKEN_KEY+Constants.SEPARATOR+userNameAndIp+requestURI,false);
        }
        LOGGER.info("uriKey："+uriKey+"获取了新的"+type+"："+newToken);
       }
        if (Objects.equals(type,Constants.TOKEN_KEY)){
            //通过返回请求头将新token返回前端
            response.setHeader(Constants.HTTP_HEADER_TOKEN_KEY, newToken);
            //告知浏览器，实际请求将携带一个自定义请求头部字段
            response.setHeader("Access-Control-Expose-Headers",Constants.HTTP_HEADER_TOKEN_KEY);
        }else if (Objects.equals(type,Constants.REFRESH_TOKEN_KEY)){
            //通过返回请求头将新token返回前端
            response.setHeader(Constants.HTTP_HEADER_REFRESH_TOKEN_KEY, newToken);
            //告知浏览器，实际请求将携带一个自定义请求头部字段
            response.setHeader("Access-Control-Expose-Headers",Constants.HTTP_HEADER_REFRESH_TOKEN_KEY);
        }
        //禁用缓存，防止前端读取到缓存内容
        response.setHeader("Cache-Control","no-store");
    }
}
